﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EuroManager.Domain.Simulator.Engine;
using EuroManager.Domain.Simulator.Helpers;
using EuroManager.Domain.Simulator.Tactics;
using EuroManager.Domain.Simulator.World;
using NUnit.Framework;
using Rhino.Mocks;

namespace EuroManager.Domain.Simulator.Tests.World
{
    [TestFixture]
    public class BallTests
    {
        [Test]
        public void ShouldMoveInRequestedDirectionAfterKick()
        {
            Vector2D location = new Vector2D(20, 50);
            Vector2D kickDirection = new Vector2D(-3, -2);

            Ball ball = new Ball(new Pitch(), location);
            ball.Kick(kickDirection);
            TestHelper.Advance(1, ball);
            
            Vector2D path = ball.Location - location;

            Assert.IsTrue(path.IsParallelWithSense(kickDirection));
        }

        [Test]
        public void ShouldSlowDownAfterKick()
        {
            Ball ball = new Ball(new Pitch(), new Vector2D(30, 50));
            ball.Kick(new Vector2D(-2, 3));

            TestHelper.Advance(0.5f, ball);
            Vector2D velocity = ball.Velocity;
            TestHelper.Advance(0.5f, ball);

            Assert.Less(ball.Velocity.Length, velocity.Length);
        }

        [Test]
        public void ShouldReachDestinationWhenKickedAtTarget()
        {
            Vector2D target = new Vector2D(40, 30);
            Ball ball = new Ball(new Pitch(), new Vector2D(10, 10));

            ball.KickAt(target, 10);
            TestHelper.AdvanceWhile(() => !Vector2D.Equals(ball.Location, target, 0.01f), 100, ball);

            Assert.IsTrue(Vector2D.Equals(ball.Location, target, 0.01f));
        }

        [Test]
        public void ShouldReachDestinationWithSpecifiedSpeedWhenKickedAtTarget()
        {
            Vector2D target = new Vector2D(40, 30);
            float speedAtTarget = 10;
            Ball ball = new Ball(new Pitch(), new Vector2D(10, 10));

            ball.KickAt(target, speedAtTarget);
            TestHelper.AdvanceWhile(() => !Vector2D.Equals(ball.Location, target, 0.005f), 100, ball);

            Assert.AreEqual(speedAtTarget, ball.Speed, 0.5f);
        }

        [Test]
        public void ShouldHaveNoOwnerOnStart()
        {
            Ball ball = new Ball(new Pitch(), Vector2D.Zero);

            Assert.IsFalse(ball.HasOwner);
            Assert.IsNull(ball.Owner);
        }

        [Test]
        public void ShouldHaveNoOwnerWhenLetFree()
        {
            FakeGameWorld world = new FakeGameWorld();
            IBall ball = world.Ball;
            Player player = world.AddPlayer();

            ball.Trap(player);
            ball.Free();

            Assert.IsFalse(ball.HasOwner);
            Assert.IsNull(ball.Owner);
        }

        [Test]
        public void ShouldExposeOwningTeam()
        {
            FakeGameWorld world = new FakeGameWorld();
            IBall ball = world.Ball;
            Player player = world.AddPlayer(world.Team2);

            ball.Trap(player);

            Assert.AreSame(world.Team2, ball.OwningTeam);
        }

        [Test]
        public void ShouldChangeOwnerWhenTrapped()
        {
            FakeGameWorld world = new FakeGameWorld();
            IBall ball = world.Ball;
            Player player1 = world.AddPlayer();
            Player player2 = world.AddPlayer();
            
            ball.Trap(player1);
            ball.Trap(player2);

            Assert.IsTrue(ball.HasOwner);
            Assert.AreSame(player2, ball.Owner);
        }

        [Test]
        public void ShouldHaltWhenTrapped()
        {
            FakeGameWorld world = new FakeGameWorld();
            IBall ball = world.Ball;
            Player player = world.AddPlayer();

            ball.Kick(new Vector2D(10, 15));
            TestHelper.Advance(0.5f, world);
            ball.Trap(player);
            world.Update(0.1f);

            Assert.IsFalse(ball.IsMoving);
        }

        [Test]
        public void ShouldBounceFromLeftPitchSide()
        {
            Vector2D velocity = new Vector2D(-20, 5);
            Vector2D expected = new Vector2D(-velocity.X, velocity.Y);
            
            Ball ball = new Ball(new Pitch(), new Vector2D(10, 10));
            ball.Kick(velocity);
            TestHelper.Advance(2, ball);

            Assert.IsTrue(ball.Direction.IsParallelWithSense(expected));
        }

        [Test]
        public void ShouldBounceFromRightPitchSide()
        {
            Vector2D velocity = new Vector2D(20, 5);
            Vector2D expected = new Vector2D(-velocity.X, velocity.Y);

            Pitch pitch = new Pitch();
            Ball ball = new Ball(pitch, new Vector2D(pitch.Width - 10, pitch.Height - 10));
            ball.Kick(velocity);
            TestHelper.Advance(2, ball);

            Assert.IsTrue(ball.Direction.IsParallelWithSense(expected));
        }

        [Test]
        public void ShouldBounceFromTopPitchSide()
        {
            Vector2D velocity = new Vector2D(5, -20);
            Vector2D expected = new Vector2D(velocity.X, -velocity.Y);

            Ball ball = new Ball(new Pitch(), new Vector2D(10, 10));
            ball.Kick(velocity);
            TestHelper.Advance(2, ball);

            Assert.IsTrue(ball.Direction.IsParallelWithSense(expected));
        }

        [Test]
        public void ShouldBounceFromBottomPitchSide()
        {
            Vector2D velocity = new Vector2D(5, 20);
            Vector2D expected = new Vector2D(velocity.X, -velocity.Y);

            Pitch pitch = new Pitch();
            Ball ball = new Ball(pitch, new Vector2D(10, pitch.Height - 10));
            ball.Kick(velocity);
            TestHelper.Advance(2, ball);

            Assert.IsTrue(ball.Direction.IsParallelWithSense(expected));
        }

        [Test]
        public void ShouldPredictFutureLocations()
        {
            Ball ball = new Ball(new Pitch(), new Vector2D(10, 20));
            
            ball.Kick(new Vector2D(15, 10));
            TestHelper.Advance(0.5f, ball);
            TimeLocation futureLocation = ball.FutureLocations.ElementAt(2);
            TestHelper.Advance(futureLocation.Time, ball);

            Assert.IsTrue(Vector2D.Equals(futureLocation.Location, ball.Location, 0.01f));
        }

        [Test]
        public void FutureLocationsShouldBePlacedOnMovementPath()
        {
            Vector2D startLocation = new Vector2D(10, 20);
            Vector2D velocity = new Vector2D(25, 10);
            
            Ball ball = new Ball(new Pitch(), startLocation);
            ball.Kick(velocity);
            TestHelper.Advance(0.5f, ball);

            Vector2D toFirst = ball.FutureLocations.First().Location - startLocation;
            Vector2D toLast = ball.FutureLocations.Last().Location - startLocation;
            
            Assert.IsTrue(toFirst.IsParallelWithSense(velocity));
            Assert.IsTrue(toLast.IsParallelWithSense(velocity));
        }

        [Test]
        public void ShouldExposeStopLocationInFutureLocations()
        {
            Ball ball = new Ball(new Pitch(), new Vector2D(10, 20));

            ball.Kick(new Vector2D(15, 10));
            TestHelper.Advance(0.5f, ball);
            TimeLocation lastFutureLocation = ball.FutureLocations.Last();
            TestHelper.Advance(lastFutureLocation.Time, ball);

            Assert.IsFalse(ball.IsMoving);
        }
    }
}
